/**
 * @Description: 请求ajax帮助函数
 * @Author BPF
 * @create 2021/8/24 13:59
 */
import axios, { CancelToken } from 'axios'
import qs from 'qs'
import profile from '@root/profile'
import useAppStore from '@store/use-app-store'
import useLayoutStore from '@store/use-layout-store'
import useMenuStore from '@store/use-menu-store'
import useUserStore from '@store/use-user-store'
import { fetchLoadingCompose } from '@shared/utils/dom' // 加载接口loading
import { _typeof, blobSaveAs, bus, hasOwn, isNullable, isPlainObject, isRegExp, parseJsonNoError, to } from 'sf-utils2'
import {
  X_USER_TOKEN,
  X_REAL_PERMISSION_CODE,
  MENU_NAME,
  REAL_PERMISSION_CODE_MAP,
  USER_TOKEN_EXPIRED_CODE,
  SERVER_TYPE,
  MENU_ADDRESS,
  REAL_IP,
  API_TIMEOUT_MAP
} from '@/utils/constants'
import { transformResponseDataValue } from '@/utils/response'
import { EXIT_APP_BASE_1001 } from '@/utils/bus-id'

import {
  axiosInstance,
  interceptorResponseCommonError,
  requestFactory,
  POSTNoLoading,
  GETNoLoading,
  DELETE,
  messageBoxAlertHOC,
  PUT,
  toSkipLoading
} from '@shared/utils/axios-util'

export {
  POSTNoLoading,
  requestFactory,
  interceptorResponseCommonError,
  axiosInstance,
  GETNoLoading,
  DELETE,
  PUT,
  messageBoxAlertHOC,
  toSkipLoading
}

axiosInstance.defaults.baseURL = profile.baseUrl

let _layoutStore, _appStore, _userStore, _messageBoxAlert // 是否已经有提示框了

/**
 * @description 获取真实权限code
 * @param config
 * @return {string}
 */
function getRealPermissionCode(config) {
  const { _authMenuObjByPath } = useMenuStore()
  const { router } = _appStore

  const pathName = router.currentRoute?.path
  const currentMenuItem = _authMenuObjByPath[pathName] // 当前菜单对象

  let realPermissionCode = currentMenuItem?.realPermissionCode

  // 赋予数据权限
  if (!hasOwn(config?.headers || {}, X_REAL_PERMISSION_CODE)) {
    // 如果headers 头没有，那么取 接口映射权限code值
    const splits = (config.url?.split?.('?') || []).filter(Boolean)
    const apiPathName = splits[0]
    const codeKey = `${pathName}|${apiPathName}`
    if (REAL_PERMISSION_CODE_MAP.has(codeKey)) {
      realPermissionCode = REAL_PERMISSION_CODE_MAP.get(codeKey)
    } else if (REAL_PERMISSION_CODE_MAP.has(apiPathName)) {
      realPermissionCode = REAL_PERMISSION_CODE_MAP.get(apiPathName)
    } else {
      REAL_PERMISSION_CODE_MAP.forEach((value, key) => {
        if (isRegExp(key) && key.test(apiPathName)) {
          realPermissionCode = value
          // break 循环
          throw new Error()
        }
      })
    }
  }
  return realPermissionCode
}

// request拦截器
axiosInstance.interceptors.request.use(
  (config) => {
    config.paramsSerializer = (params) => {
      return qs.stringify(params, { arrayFormat: 'repeat' })
    }
    // console.log('config', config)
    config.url = config.url.replace(/\/\//g, '/')
    _appStore ||= useAppStore()
    _userStore ||= useUserStore()

    if (!profile.IS_PRO && config.url.includes('/mock/api/')) {
      // 开发环境，走的是mock数据
      const suffixUrls = config.url.split('/mock/api/').filter(Boolean)
      const mockUrl = '/mock/api/' + (suffixUrls[1] || suffixUrls[0])
      config.url = origin + mockUrl
    }

    const { _authMenuObjByPath } = useMenuStore()
    const { router } = _appStore

    const pathName = router.currentRoute?.path
    const currentMenuItem = _authMenuObjByPath[pathName] // 当前菜单对象

    const realPermissionCode = getRealPermissionCode(config)

    // /mock/api/

    // appStore.token = `Y2IyMDcyYTA0NGY3MWNiNWMyYzA3MGVjZjY0NjIwZjY0ZTIxNGYyMTgwYzE1YjM4ZWE3OTM2YmVlZTU3NDU5NQ==`
    return {
      ...config,
      headers: {
        /**
         * token
         */
        [X_USER_TOKEN]: _userStore?.token,
        /**
         * 后端权限code
         */
        [X_REAL_PERMISSION_CODE]: realPermissionCode,

        /**
         * 当前路径
         */
        [MENU_ADDRESS]: window.location.href?.split?.('?')?.[0],

        /**
         * 公网的ip
         */
        [REAL_IP]: _appStore?.publicIp || '',

        /**
         * 菜单名称
         */
        [MENU_NAME]: currentMenuItem
          ? encodeURIComponent(currentMenuItem.menuName)
          : encodeURIComponent(router.currentRoute.meta.title || ''), // 当前菜单名称
        /**
         * 服务器类型: 资管端-10 处置端-20
         */
        [SERVER_TYPE]: '10',

        /**
         * 自定义header头部
         */
        ...config.headers,
        ..._appStore?.appHeaderInfo
      }
    }
  },
  (error) => {
    return Promise.reject(error)
  }
)

/**
 * 1、请求拦截器
 */
axiosInstance.interceptors.request.use((config) => {
  const apiPathName = (config.url || '').split('?').filter(Boolean)?.[0] || ''

  // 设置timeout
  if (API_TIMEOUT_MAP.has(apiPathName)) {
    config.timeout = API_TIMEOUT_MAP.get(apiPathName)
  }
  return config
})

// response 拦截器
axiosInstance.interceptors.response.use((response) => {
  const res = response.data
  _appStore ||= useAppStore()

  _messageBoxAlert ||= messageBoxAlertHOC()

  if (USER_TOKEN_EXPIRED_CODE[res?.code]) {
    // fix 在登录页面捕捉到异常，不要重定向到login页面，这样会导致页面刷新，丢失用户输入的数据
    const pathName = _appStore.router.currentRoute.path
    if (pathName == '/login') return Promise.reject(res?.message || '登录已过期, 请重新登录')

    _messageBoxAlert(res?.message, '提示', {
      type: 'error',
      confirmButtonText: '确定',
      callback: async () => {
        _userStore ||= useUserStore()
        await _userStore.loginOut(0)

        // 退出法诉平台
        bus.emit(EXIT_APP_BASE_1001)

        if (['dev', 'sit', 'uat', 'prd'].includes(profile.APP_MODE)) {
          // 如果是sit环境，跳转到新核心的登录页面
          window.location.href = profile.newCoreAddress + '/login?systemId=cf-manage'
        } else {
          _appStore.router.push('/login')
          window.location.reload()
        }

        // window.location.href = profile.newCoreAddress
      }
    })
    return Promise.reject(res?.message || '登录已过期, 请重新登录')
  }

  // 无响应数据
  if (res === '' || isNullable(res)) response.data = { code: -500, message: '无响应数据' }
  transformResponseDataValue(response)
  return response
}, interceptorResponseCommonError)

/**
 * post 方式请求loading
 * @param {string} url
 * @param {Record<string, any>} [params]
 * @param {Record<string, any>} [data]
 * @param {Record<string, any>} [headers]
 * @param {boolean} loading 是否开启loading
 * @param {any} [others]
 * @returns {Promise<any> & {cancel?: () => void, loading?: boolean}}
 * @constructor
 * @example
 * ```js
 *   mockTableData: (body = { params: {}, data: {}, headers: {} }) =>
 *     POST({ url: mock.prefix + '/api/zsap-operation-user/oprEmployeeInfo/selectByPage', ...body }),
 * ```
 *
 */
export function POST({ url, params = {}, data = {}, headers = {}, loading = true, ...others }) {
  _layoutStore ||= useLayoutStore()
  _appStore ||= useAppStore()

  if (loading === false) return POSTNoLoading(...arguments)
  const resolve = () => _appStore.router.resolve(_appStore.router.currentRoute.fullPath)
  const route = resolve()
  let cancel
  const promiseRef = { value: undefined }
  const fetchFn = fetchLoadingCompose({
    store: _layoutStore,
    resolve,
    route,
    fetchFunc: () => {
      const requestObj = requestFactory({ url, method: 'POST', data, params, headers, ...others })
      cancel = requestObj.cancel
      return requestObj
    },
    promiseRef
  })
  fetchFn.cancel = cancel
  _updateFetchPromiseLoading(fetchFn, 'loading', promiseRef)
  return fetchFn
}

/**
 * get 方式请求loading
 * @param {string} url
 * @param {Record<string, any>} [params]
 * @param {Record<string, any>} [data]
 * @param {Record<string, any>} [headers]
 * @param {any} [others]
 * @returns {Promise<any> & {cancel?: () => void, loading?: boolean}}
 * @constructor
 * ```js
 *   mockTableData: (body = { params: {}, data: {}, headers: {} }) =>
 *     GET({ url: mock.prefix + '/api/zsap-operation-user/oprEmployeeInfo/selectByPage', ...body }),
 * ```
 */
export const GET = ({ url, params = {}, data = {}, headers = {}, ...others }) => {
  _layoutStore ||= useLayoutStore()
  _appStore ||= useAppStore()
  const resolve = () => _appStore.router.resolve(_appStore.router.currentRoute.fullPath)
  const route = resolve()
  let cancel
  const promiseRef = { value: undefined }
  const fetchFn = fetchLoadingCompose({
    store: _layoutStore,
    resolve,
    route,
    fetchFunc: () => {
      const requestObj = requestFactory({ url, method: 'GET', data, params, headers, ...others })
      cancel = requestObj.cancel
      return requestObj
    }
  })
  fetchFn.cancel = cancel
  _updateFetchPromiseLoading(fetchFn, 'loading', promiseRef)
  return fetchFn
}

/**
 * 下载文件 支持异步
 * @param url 地址
 * @param fileName 文件名
 * @param {'post'|'get'} method 请求方式
 * @param {object} data  如果是post请求，传入data
 * @param {object} params  如果是get请求，传入params
 * @param  {object} headers 请求头
 * @param {object} rest  剩余参数
 * @returns {Promise<any> & {cancel?: () => void, loading?: boolean}}
 *
 * @example
 *
 * ```js
 *
 * 接口申明：download({url: cf.prefix + '/manageAssetAccount/getCaseNoTemplate', fileName: '读书管理.pdf'})
 *
 * const downloadApi  = download({url: cf.prefix + '/manageAssetAccount/getCaseNoTemplate', fileName: '读书管理.pdf'})
 * const [res, err] = await to(downloadApi)
 * if (err || res?.code !== 200) return Message.error(res?.message || String(err))
 *
 * ```
 */
export const Download = ({ url, fileName, method = 'post', data, params, headers, ...rest }) => {
  let cancel
  const fetchFunc = async () => {
    const requestObj = requestFactory({
      url,
      method,
      data,
      params,
      headers,
      ...rest,
      responseType: 'blob'
    })
    cancel = requestObj.cancel
    const [res, err] = await to(requestObj)
    if (err) return Promise.reject(err)
    const { data: responseData } = res || {}
    if (isPlainObject(responseData) && responseData?.code) {
      return Promise.resolve({ data: { code: 200, data: responseData } })
    }
    if (_typeof(responseData) == 'Blob') {
      if (responseData.type == 'application/json') {
        // 如果是json数据
        const text = await responseData.text()
        return Promise.resolve({ data: parseJsonNoError(text) })
      }
      blobSaveAs(responseData, fileName)
    } else return Promise.reject(new Error('数据非二进制流'))
    return Promise.resolve({ data: { code: 200, data: responseData } })
  }
  fetchFunc.cancel = cancel

  _layoutStore ||= useLayoutStore()
  _appStore ||= useAppStore()
  const resolve = () => _appStore.router.resolve(_appStore.router.currentRoute.fullPath)
  const route = resolve()
  const promiseRef = { value: undefined }
  const fetchFn = fetchLoadingCompose({
    store: _layoutStore,
    resolve,
    route,
    fetchFunc
  })
  fetchFn.cancel = cancel
  _updateFetchPromiseLoading(fetchFn, 'loading', promiseRef)
  return fetchFn
}

/**
 * 创建一个实例
 */
export function createInstance() {
  // 创建axios实例
  const instance = axios.create({
    timeout: 2 * 60 * 1000 // 请求超时时间 默认设置了两分钟
  })

  axiosInstance.defaults.headers.post['Content-Type'] = 'application/json' // 'application/json';
  axiosInstance.defaults.baseURL = profile.baseUrl

  /**
   * 请求 工厂
   * @param options
   * @return {AxiosPromise<any>}
   */
  function requestFactory(options = {}) {
    let cancel
    if (!options?.cancelToken) {
      options.cancelToken = new CancelToken((c) => {
        cancel = c
      })
    }
    const requestObj = instance(options)
    requestObj.cancel = (args0) => cancel(args0 || { message: '请求被取消', path: options.url, options })
    return requestObj
  }
  /**
   * post 请求
   */
  const POSTNoLoading = async ({ url, params = {}, data = {}, headers = {}, ...others }) => {
    const [res, err] = await to(requestFactory({ url, method: 'POST', data, params, headers, ...others }))
    if (err) return Promise.reject(err)
    return Promise.resolve(res?.data)
  }

  /**
   * get请求，不会产生loading
   * @constructor
   */
  const GETNoLoading = async ({ url, params = {}, data = {}, headers = {}, ...others }) => {
    const [res, err] = await to(requestFactory({ url, method: 'GET', data, params, headers, ...others }))
    if (err) return Promise.reject(err)
    return Promise.resolve(res?.data)
  }

  return {
    POSTNoLoading,
    GETNoLoading,
    requestFactory,
    instance
  }
}

/**
 * 更新promise loading状态
 * @param fetchFn
 * @param key
 * @param promiseRef
 */
function _updateFetchPromiseLoading(fetchFn, key = 'loading', promiseRef) {
  Object.defineProperty(fetchFn, key, {
    get: () => promiseRef.value?.loading,
    set: (val) => promiseRef.value && (promiseRef.value.loading = val),
    enumerable: true
  })
}

/**
 * 错误code
 */
export const REQUEST_ERROR_CODE_MAP = {
  /** 路由取消 */
  ERR_CANCELED: 'ERR_CANCELED'
}
